/*
https://wivl.github.io/blog/posts/cpp17-multi-thread-zero-to-threadpool/
条件变量
线程间的同步不只有互斥，有时我们需要一个线程等待某个条件满足。条件变量就是干这个的
条件变量是利用线程间共享的全局变量进行同步的一种机制，主要包括两个动作：
1，一个线程等待条件变量的条件成立而挂起;
2，另一个线程使条件成立（给出条件成立信号）。

condition_variable条件变量可以阻塞（wait、wait_for、wait_until）调用的线程直到使用（notify_one或notify_all）通知恢复为止。
头文件<condition_variable>
- condition_variable
​- condition_variable_any
相同点：两者都能与std::mutex一起使用。
不同点：前者仅限于与 std::mutex 一起工作，而后者可以和任何满足最低标准的互斥量一起工作，从而加上了_any的后缀。condition_variable_any会产生额外的开销。

注意：
1，一般只推荐使用condition_variable。除非对灵活性有硬性要求，才会考虑condition_variable_any。
2，condition_variable必须结合unique_lock使用
3，condition_variable是一个类，这个类既有构造函数也有析构函数，使用时需要构造对应condition_variable对象

函数                          说明
condition_variable()          构建对象
~condition_variable()         删除
wait()                        wait until notified
wait_for                      wait for timeout or until notified
wait_nutil                    wait until notified or time point
notify_one                   解锁一个线程，如果有多个则未知哪个线程执行
notify_all                   解锁所有线程
cv_status                    这是一个元类，表示variable的状态

wait 方法带谓词的版本被更广泛地使用，因为如果使用第一种不带谓词的 wait 方法而不反复检查条件的话，可能会造成虚假唤醒（spurious wakeup）。
考虑一种可能发生的情况：假设存在多个消费者线程阻塞等待，生产者调用 notify_* 方法通知阻塞的消费者线程。
即使某个消费者线程的条件在调用 notify_* 时是满足的，另一个线程可能在当前线程被唤醒并重新获取锁之前改变了共享资源的状态，导致条件不再满足。
因此，在多线程环境中，条件变量等待的核心规则是等待线程自己检查条件是否满足，而不能完全依赖 notify_* 的调用。也因此，wait 方法通常需要一个谓词函数来重复检查条件。
*/

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>

// 生产者 消费者例子使用条件变量

std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;
const int max_size = 10;

// 生产者函数
// 如果缓冲区已满(buffer.size() >= max_size), 调用cv.wait()阻塞，直到缓冲区有空间
// 生产一个数据后，通过cv.notify_all()通知消费者数据可用
void producer(int id)
{
  int item = 0;
  while (item < 20)
  {
    // 使用 unique_lock 保护 buffer，避免竞争条件
    std::unique_lock<std::mutex> lock(mtx);
    // 调用wait方法后，先释放锁，使得其他线程可以访问共享资源
    // 当被唤醒之后，wait会自动重新获取锁，确保被唤醒的线程能安全的检查条件
    cv.wait(lock, [] { return buffer.size() < max_size; }); // 等待空间可用
    buffer.push(item);
    std::cout << "Producer " << id << " produced: " << item << std::endl;
    item++;
    cv.notify_all(); // 通知消费者
    lock.unlock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时
  }
}

// 消费者函数
// 如果缓冲区为空(buffer.empty()) 调用cv.wait()阻塞，直到缓冲区有数据
// 消费一个数据后，通过cv.notify_all()通知生产者可以继续生产
void consumer(int id)
{
  while(true)
  {
    // 使用unique_lock保护buffer, 避免竞争条件
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] {return !buffer.empty();});  //等待数据可用
    int item = buffer.front();
    buffer.pop();
    std::cout << "Consumer " << id << " consumed: " << item << std::endl;

    cv.notify_all(); // 通知生产者
    lock.unlock();
    std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费耗时
  }
}

int main() {
    std::thread p1(producer, 1);
    std::thread p2(producer, 2);
    std::thread c1(consumer, 1);
    std::thread c2(consumer, 2);
    
    p1.join();
    p2.join();
    c1.detach(); // 消费者在生产结束后继续消费剩余的数据
    c2.detach();

    return 0;
}